﻿#region Creditos

/* Autor: Edilberto Sánchez Forero	*/
/* Email: esanchezfo@gmail.com		*/
/* Licencia: LGPL, Software Libre	*/
/* País: Colombia, Bogotá			*/

#endregion

#region Librerias

using System;
using System.Collections.Generic;
using System.Xml;
using System.Collections.Specialized;
using System.IO;

using ESF.Object.XmlRepresenter.Rules;
using ESF.General;
using ESF.Object.XmlRepresenter.Extensions.Description;
using ESF.General.Text;

#endregion


namespace ESF.Object.XmlRepresenter
{
	public sealed class XmlObject : IEquatable<XmlObject>, IComparable<XmlObject>
	{
		#region Constantes
		
		public const byte BYTE_MAXLENGTH = 3;
		public const byte USHORT_MAXLENGTH = 5;
		public const byte UINT_MAXLENGTH = 10;
		public const byte ULONG_MAXLENGTH = 20;
		
		public const byte ALIAS_MAXLENGTH = 8;
		
		#region Constantes para Descripciones
		
		public const byte ISOLANGUAGECODE_LENGTH = 2;
		
		#endregion
		
		#endregion
		
	
		#region Delegados
		
		public delegate bool ElementExistsChecker (params string [] pstrElementNames);
		
		public delegate bool MembersElementExistsChecker (string pstrElementName, List<XmlMember> pstrMembers, out List<XmlMember> pobjMembersNotFound);
		
		public delegate bool MemberElementExistsChecker (string pstrElementName, string pstrMemberName, out ValueTypes? pobjElementValueType);
		
		#endregion
		
	
		#region Miembros
		
		private string _Name;
		private string _SetName;
		private string _NameMemberName;
		private string _AbreviatureMemberName;
		private string _EnabledMemberName;
		private string _Type;
		private string _Alias;
		private string _ToShortStringMemberNames;
		
		public double Level;
		
		private bool _IsArray;
		private bool _IsBasic;
		
		private byte? _IDMaxLength;
		
		private IDTypes? _IDType;
		
		private List<XmlMember> _Members;
		private List<XmlMember> _ToShortStringMembers;
		
		private int _LineNumber;
		private int _LinePosition;	
		
		private List<XmlObjectDescription> _Descriptions;	
		
		private bool _DescriptionsClosed;
		
		private List<string> _DescriptionISOLanguageCodes;
		
		#endregion
		
		
		#region Elementos
		
		public List<UniqueRule> UniqueRules;
		public List<OnlyOneRule> OnlyOneRules;
		
		#endregion
		
		
		#region Propiedades
		
		public List<XmlObjectDescription> Descriptions
		{
			get
			{
				if (!this._DescriptionsClosed)
				{
					throw new GenericException (GenericException.CRI_OBJECTCOLLECTION_MUSTBECLOSED, string.Format ("El listado de descripciones del objeto {0} no ha sido cerrado", this._Name));
				}
				
				return this._Descriptions;
			}
		}
		
		
		public bool IsBasic { get { return this._IsBasic; } }
				
				
		public List<XmlMember> ShortQueryMembers
		{
			get
			{
				List<XmlMember> objResult;
			
				
				objResult = this._Members.FindAll (m => m.IsForShortQuery);
				
				if (objResult.Count == 0)
				{
					return this._Members;
				}
				
				return objResult;
			}
		}
		
		
		public List<XmlMember> ToShortStringMembers
		{
			get
			{
				List<XmlMember> objResult;
			
				
				objResult = this._ToShortStringMembers;
				
				if (objResult.Count == 0)
				{
					return this._Members;
				}
				
				return objResult;
			}
		}
				
		
		public List<XmlMember> CustomMembers
		{
			get
			{
				return this._Members.FindAll (m => m.Type == ValueTypes.Custom);
			}
		}
		
		
		public List<XmlMember> NoCustomMembers
		{
			get
			{
				return this._Members.FindAll (m => m.Type != ValueTypes.Custom);
			}
		}

		
		public string MembersCSV
		{
			get
			{
				string strResult;
				
				
				strResult = string.Empty;
				
				foreach (XmlMember objMember in this._Members)
				{
					strResult += objMember.Name + ",";
				}
				
				return strResult.Substring (0, strResult.Length - 1);
			}
		}
		
		public string Alias 
		{
			get 
			{
				if (string.IsNullOrEmpty (this._Alias))
				{
					return this._Name;
				}
				
				return this._Alias; 
			}
		}
		
		public string Name { get { return this._Name; } }
		
		public string SetName { get { return this._SetName; } }
		
		public XmlMember NameMember 
		{
			get 
			{
				if (this.HasNameMember)
				{
					return this [this._NameMemberName]; 
				}
				
				return null;
			}
		}
		
		public XmlMember AbreviatureMember 
		{
			get
			{
				if (this.HasAbreviatureMember)
				{
					return this [this._AbreviatureMemberName]; 
				}
			
				return null;
			}
		}
		
		public XmlMember EnabledMember 
		{
			get
			{
				if (this.HasEnabledMember)
				{
					return this [this._EnabledMemberName]; 
				}
			
				return null;
			}
		}
		
		public bool HasNameMember { get { return !string.IsNullOrEmpty (this._NameMemberName); } }
		
		public bool HasAbreviatureMember { get { return !string.IsNullOrEmpty (this._AbreviatureMemberName); } }
		
		public bool HasEnabledMember { get { return !string.IsNullOrEmpty (this._EnabledMemberName); } }
		
		public string Type { get { return this._Type; } }		
				
		public string FullName { get { return string.Format ("{0}.{1}", this._SetName, this._Name); } }
		
				
		public XmlMember this [string pstrMemberName]
		{
			get
			{
				XmlMember objMember;
				
			
				if (string.IsNullOrEmpty (pstrMemberName))
				{
					return null;
				}
				
				objMember = this._Members.Find (m => m.Name == pstrMemberName);
				
				if (objMember == null)
				{
					throw new GenericException (GenericException.CRI_UNDEFINED, string.Format ("No hay un miembro {0} registrado en el objeto {1}", pstrMemberName, this._Name));
				}
				
				return objMember;
			}
		}
				
				
		public byte? IDMaxLength { get { return this._IDMaxLength; } }
			
		
		public bool IsArray { get { return this._IsArray; } }
		
			
		public List<XmlMember> Members { get { return this._Members; } }
		
		
		public IDTypes? IDType 
		{
			get 
			{
				return this._IDType;
			}
		}
		
		
		public List<string> DescriptionsISOLanguageCodes
		{
			get
			{
				return this._DescriptionISOLanguageCodes;
			}
		}
		
		#endregion
		
		
		#region Constructores
		
		public XmlObject (string pstrSetName, ref XmlTextReader pxmlReader)
		{
			object objTemp;
		
			XmlHelper xmlEx;
			
		
			this._Name = pxmlReader.Name;
			this._SetName = pstrSetName;
			
			this._LineNumber = pxmlReader.LineNumber;
			this._LinePosition = pxmlReader.LinePosition;
						
						
			this._Members = new List<XmlMember> ();
			this._ToShortStringMembers = new List<XmlMember> ();
			
			this.UniqueRules = new List<UniqueRule> ();
			this.OnlyOneRules = new List<OnlyOneRule> ();
			
			this._DescriptionISOLanguageCodes = new List<string> ();
			
			#region Lectura de Atributos
			
			xmlEx = new XmlHelper ();
			xmlEx.ObjectName = this.FullName;
			xmlEx.EvaluatedNodeReader = pxmlReader;
			
			objTemp = xmlEx.GetAttributeEnum (false, "IDType", "Tipo de ID", typeof (IDTypes), false);
			
			if (objTemp != null) { this._IDType = (IDTypes) objTemp; }
			
			this._IDMaxLength = xmlEx.GetAttributeValueByte (false, "IDMaxLength", "Longitud máxima del ID");
			
			this._Alias = xmlEx.GetAttributeValue (false, "alias", "Alias", XmlObject.ALIAS_MAXLENGTH);
			
			this._AbreviatureMemberName = xmlEx.GetAttributeValue (false, "abreviatureMember", "Nombre del Miembro de Abreviatura");
			
			this._NameMemberName = xmlEx.GetAttributeValue (false, "nameMember", "Nombre del Miembro de Nombre");
			
			this._EnabledMemberName = xmlEx.GetAttributeValue (false, "enabledMember", "Nombre del Miembro de Habilitado");
			
			this._IsArray = xmlEx.GetAttributeValueBool (false, "isArray", "Indicador de si es un arreglo", false).Value;
			
			this._Type = xmlEx.GetAttributeValue (false, "type", "Tipo");
			
			if (!this._IsArray)
			{
				this._ToShortStringMemberNames = xmlEx.GetAttributeValue (false, "toShortStringMembers", "Listado de Miembros para utilizarse en la función ToShortString");
			
				if (string.IsNullOrEmpty (this._ToShortStringMemberNames))
				{
					this._ToShortStringMemberNames = this._AbreviatureMemberName;
				}
				
				xmlEx.Validate.Filled (false, "Listado de Miembros para utilizarse en la función ToShortString", this._ToShortStringMemberNames);
			}
			
			#endregion
			
			if (this._IsArray)
			{
				xmlEx.Validate.Filled (true, "Tipo (type) requerido para el arreglo", this._Type);
			}
			
			if (string.IsNullOrEmpty (this._Type))
			{
				this._Type = this._Name;
			}
			
			this._Descriptions = new List<XmlObjectDescription> ();
		}
		
		#endregion
		
		
		#region Funciones
		
		public void IncreaseLevel ()
		{
		    this.IncreaseLevel (1);
		}
			
		public void IncreaseLevel (double pdblIncrement)
		{
		    this.Level += pdblIncrement;
		}
		
				
		public void CheckAfterLoad 
		(
			ElementExistsChecker pdelElementExistsChecker, 
			MembersElementExistsChecker pdelMembersElementExistsChecker,
			MemberElementExistsChecker pdelMemberElementExistsChecker
		)
		{
			Validate objVal;
			
			
			objVal = new Validate ();
			objVal.ObjectName = this._Name;
			
			
			#region Validacion de Delegados
			
			if (pdelElementExistsChecker == null)
			{
				throw new ArgumentNullException ("pdelElementChecker", "No esta definido el delegado: ElementChecker");
			}
			
			if (pdelMembersElementExistsChecker == null)
			{
				throw new ArgumentNullException ("pdelMemberElementExistsChecker", "No esta definido el delegado: ElementExistsChecker");
			}
			
			#endregion
			
			#region Validacion de Elemento con Tipo Local
			
			this.Members.
				FindAll (m => string.IsNullOrEmpty (m.FromSet) && 
					!string.IsNullOrEmpty (m.CustomType)).
				ForEach (
					delegate (XmlMember pobjMember)
					{
						if (!pdelElementExistsChecker (pobjMember.CustomType))
						{
							throw new GenericException (GenericException.CRI_INVALID_REFERENCE, 
								string.Format ("El tipo {0} de {1} no esta definido localmente", pobjMember.CustomType, pobjMember.Name), 
								this._LineNumber, this._LinePosition);
						}
					}
			);
			
			#endregion
			
			#region Validacion del ID
			
			if (this._IDMaxLength != null)
			{
				if (this._IDType == null)
				{
					throw new GenericException (GenericException.CRI_INVALID_ATTRIBUTE, "Requiere el atributo 'IDType'", this._LineNumber, this._LinePosition);
				}
				else
				{
					switch (this._IDType.Value)
					{
						case IDTypes.Byte:
							if (this._IDMaxLength.Value > XmlObject.BYTE_MAXLENGTH)
							{
								throw new GenericException (GenericException.CRI_INVALID_ATTRIBUTE, 
									string.Format ("La longitud para un ID de tipo {0} no puede contener mas de {1} digitos pero se le definieron {2}", this._IDType.Value, XmlObject.BYTE_MAXLENGTH, this._IDMaxLength.Value),
									this._LineNumber, this._LinePosition);
							}
							break;
							
						case IDTypes.UInt32:
							if (this._IDMaxLength.Value > XmlObject.UINT_MAXLENGTH)
							{
								throw new GenericException (GenericException.CRI_INVALID_ATTRIBUTE, 
									string.Format ("La longitud para un ID de tipo {0} no puede contener mas de {1} digitos pero se le definieron {2}", this._IDType.Value, XmlObject.UINT_MAXLENGTH, this._IDMaxLength.Value),
									this._LineNumber, this._LinePosition);
							}
							break;
						
						case IDTypes.UInt64:
							if (this._IDMaxLength.Value > XmlObject.ULONG_MAXLENGTH)
							{
								throw new GenericException (GenericException.CRI_INVALID_ATTRIBUTE, 
									string.Format ("La longitud para un ID de tipo {0} no puede contener mas de {1} digitos pero se le definieron {2}", this._IDType.Value, XmlObject.ULONG_MAXLENGTH, this._IDMaxLength.Value),
									this._LineNumber, this._LinePosition);
							}
							break;
							
						case IDTypes.UInt16:
							if (this._IDMaxLength.Value > XmlObject.USHORT_MAXLENGTH)
							{
								throw new GenericException (GenericException.CRI_INVALID_ATTRIBUTE, 
									string.Format ("La longitud para un ID de tipo {0} no puede contener mas de {1} digitos pero se le definieron {2}", this._IDType.Value, XmlObject.USHORT_MAXLENGTH, this._IDMaxLength.Value),
									this._LineNumber, this._LinePosition);
							}
							break;
							
						case IDTypes.Guid:
							if (this._IDMaxLength.Value != TextHelper.GUID_LENGTH)
							{
								throw new GenericException (GenericException.CRI_INVALID_ATTRIBUTE, 
									string.Format ("La longitud para un ID de tipo {0} debe contener exactamente {2} caracteres pero se le definieron {1}", this._IDType.Value, XmlObject.USHORT_MAXLENGTH, this._IDMaxLength.Value),
									this._LineNumber, this._LinePosition);
							}
							break;
					}
				}
			}
			
			#endregion
			
			#region Validacion de Elemento como Arreglo de Elementos
			
			if (this.IsArray)
			{
				// Se revisa por la existencia del tipo del arreglo
				if (!pdelElementExistsChecker (this._Type))
				{
					throw new GenericException (GenericException.CRI_INVALID_DEFINITION, string.Format ("El Elemento {0} no esta definido para ser utilizado como arreglo", this._Type), this._LineNumber, this._LinePosition);
				}
			}	
			
			#endregion			
			
			#region Validacion de Elementos definidos en las reglas
			
			List<XmlMember> objMembersNotFound;
			
			string strMemberNotFoundNames;
			
			
			strMemberNotFoundNames = string.Empty;
			
			// Se revisa por la validez de los elementos utilizados para definir las reglas
			foreach (UniqueRule objUniqueRule in this.UniqueRules)
			{
				if (!pdelMembersElementExistsChecker (this._Type, objUniqueRule.Members, out objMembersNotFound))
				{
					foreach (XmlMember objMember in objMembersNotFound)
					{
						strMemberNotFoundNames = objMember.Name + ",";
					}
			
					throw new GenericException (GenericException.CRI_INVALID_ATTRIBUTE, 
						string.Format ("El(los) miembro(s): {0} de la regla no esta(n) definido(s) en el objeto {1}", strMemberNotFoundNames, this._Type), 
						this._LineNumber, this._LinePosition);
				}
			}
			
			#endregion			
				
			#region Validacion de Elementos de Abreviatura y Nombre
			
			bool blnAbreviatureMemberFound;
			bool blnNameMemberFound;
			bool blnEnabledMemberFound;
			
			blnAbreviatureMemberFound = false;
			blnNameMemberFound = false;
			blnEnabledMemberFound = false;
		
			// Se valida que el miembro marcado como abreviatura y nombre esten como miembros
			foreach (XmlMember objMember in this.Members)
			{
				if (!string.IsNullOrEmpty (this._AbreviatureMemberName))
				{
					if (objMember.Name == this._AbreviatureMemberName)
					{
						blnAbreviatureMemberFound = true;
					}
				}
				
				if (!string.IsNullOrEmpty (this._NameMemberName))
				{
					if (objMember.Name == this._NameMemberName)
					{
						blnNameMemberFound = true;
					}
				}
				
				if (!string.IsNullOrEmpty (this._EnabledMemberName))
				{
					if (objMember.Name == this._EnabledMemberName)
					{
						blnEnabledMemberFound = true;
					}
				}
			}
			
			if (!string.IsNullOrEmpty (this._AbreviatureMemberName) && !blnAbreviatureMemberFound)
			{
				throw new GenericException (GenericException.CRI_INVALID_ATTRIBUTE, 
					string.Format ("The Abreviature Member called '{0}' was not found", this._AbreviatureMemberName), 
					this._LineNumber, this._LinePosition);
			}
			
			if (!string.IsNullOrEmpty (this._NameMemberName) && !blnNameMemberFound)
			{
				throw new GenericException (GenericException.CRI_INVALID_ATTRIBUTE, 
					string.Format ("The Name Member called '{0}' was not found", this._NameMemberName), 
					this._LineNumber, this._LinePosition);
			}
			
			if (!string.IsNullOrEmpty (this._EnabledMemberName) && !blnEnabledMemberFound)
			{
				throw new GenericException (GenericException.CRI_INVALID_ATTRIBUTE, 
					string.Format ("The Enabled Member called '{0}' was not found", this._EnabledMemberName), 
					this._LineNumber, this._LinePosition);
			}
			
			#endregion
						
			#region OBSOLETO: Validacion de las reglas OnlyOne
					
			//foreach (OnlyOneRule objOnlyOneRule in this.OnlyOneRules)
			//{
			//    // Se valida la correctitud del tipo de dato del valor de uniqueValue al igual que la existencia del elemento de la regla
			//        if (!string.IsNullOrEmpty (objOnlyOneRule.UniqueValue))
			//        {
			//            //if (!pdelMemberElementExistsChecker (this._Type, objOnlyOneRule.Member.Name, out objMemberValueType))
			//            //{
			//            //    throw new GenericException (GenericException.CRI_INVALID_ATTRIBUTE, 
			//            //        string.Format ("El Miembro {0} no esta registrado en el objeto {1}", objOnlyOneRule.Member, this._Type), 
			//            //        this._LineNumber, this._LinePosition);
			//            //}
						
			//            #region Revision de Tipos
						
			//            // Se revisa si el tipo de dato del defaultValue corresponde al tipo de dato del miembro
						
			//            #endregion
			//        }
			//    }
			//}
			
			#endregion
			
			#region Deteccion de Nivel basico
			
			this.Level = 0;
			this._IsBasic = (!this.IsArray && this.CustomMembers.Count == 0);
			
			#endregion
			
			#region Validacion de Miembros para ToShortString
			
			if (!string.IsNullOrEmpty (this._ToShortStringMemberNames))
			{
				try
				{
					foreach (string strMemberName in this._ToShortStringMemberNames.Replace (" ", string.Empty).Split (','))
					{
						this._ToShortStringMembers.Add (this [strMemberName]);
					}
				}
				catch (GenericException genEx)
				{
					genEx.ChangeMessage ("Miembros invalidos para ToShortString: " + genEx.Message);
					
					throw genEx;
				}
			}
			
			#endregion
		}
		
		
		public void ToXml (ref XmlTextWriter pxmlWriter, bool pblnIncludeGeneralAttributes, bool pblnIncludeMembers, bool pblnIncludeMemberAttributes, bool pblnIncludeRules)
		{
			// TODO: Actualizar esta salida XML
		
			pxmlWriter.WriteStartElement (this.Name);
				
			if (pblnIncludeMembers)
			{
				foreach (XmlMember objMember in this.Members)
				{
					objMember.ToXml (ref pxmlWriter, pblnIncludeMemberAttributes);
				}
			}
			
			if (pblnIncludeRules)
			{
				pxmlWriter.WriteStartElement ("Rules");
				
				foreach (UniqueRule objUnique in this.UniqueRules)
				{
					objUnique.ToXml (ref pxmlWriter);
				}
				
				foreach (OnlyOneRule objOnly in this.OnlyOneRules)
				{
					objOnly.ToXml (ref pxmlWriter);
				}					
				
				pxmlWriter.WriteEndElement ();
			}
			
			pxmlWriter.WriteEndElement ();
		}


		public override string ToString ()
		{
			string strMensaje;
			
			
			strMensaje = string.Format ("{0} (Level = {1}", this.FullName, this.Level);
		
			if (this._IsArray)
			{
				strMensaje += ", IsArray";
			}
			
			if (this._IsBasic)
			{
				strMensaje += ", IsBasic";
			}
			
			return strMensaje + ")";
		}
		
		#endregion
		
		
		#region Lanzamiento de Errores
		
		private void ThrowException (string pstrMessage)
		{
			
		}
		
		
		private void ThrowException (string pstrCode, string pstrMessage)
		{
			
		}
		
		#endregion

	
		#region Funciones para la gestion de descripciones
		
		internal void CloseDescriptions (XmlObjectSetManager pobjObjectSetManager)
		{
			int intI;
			
			
			if (this._DescriptionsClosed) { return; }			
		
			for (intI = 0; intI < this._Descriptions.Count; intI ++)
			{
				this._Descriptions [intI] = this.AdjustDescription (this._Descriptions [intI]);
			}
			
			foreach (XmlMember objMember in this._Members)
			{
				objMember.CloseDescriptions (pobjObjectSetManager);
			}
			
			this._DescriptionsClosed = true;
		}
		
		
		public XmlObjectDescription GetDescription ()
		{
			if (!this._DescriptionsClosed)
			{
				throw new GenericException (GenericException.CRI_OBJECTCOLLECTION_MUSTBECLOSED, string.Format ("El listado de descripciones del objeto {0} no ha sido cerrado", this._Name));
			}
		
			return this.AdjustDescription (this._Descriptions.Find (d => d.Default));
		}
		
		
		public XmlObjectDescription GetDescription (string pstrISOLanguageCode)
		{
			if (!this._DescriptionsClosed)
			{
				throw new GenericException (GenericException.CRI_OBJECTCOLLECTION_MUSTBECLOSED, string.Format ("El listado de descripciones del objeto {0} no ha sido cerrado", this._Name));
			}
			
			return this.AdjustDescription (this._Descriptions.Find (d => d.ISOLanguageCode == pstrISOLanguageCode));
		}
		
		
		private XmlObjectDescription AdjustDescription (XmlObjectDescription pobjDescription)
		{
			if (pobjDescription == null)
			{
				return new XmlObjectDescription (this._Name);
			}
			
			return pobjDescription;
		}
		
		
		internal void AddDescription (XmlObjectDescription pobjDescription)
		{
			if (this._DescriptionsClosed)
			{
				throw new GenericException (GenericException.CRI_OBJECTCOLLECTION_CLOSED, string.Format ("El listado de descripciones del objeto {0} esta cerrado, no admite nuevas", this._Name));
			}
			
			if (pobjDescription.ObjectName != this.Name)
			{
				throw new GenericException (GenericException.CRI_INVALID_ELEMENT, string.Format ("La descripcion del objeto {0} que esta agregando no corresponde a este objeto {1}", pobjDescription.ObjectName, this.Name));
			}
		
			this._Descriptions.Add (pobjDescription);
			
			if (!this._DescriptionISOLanguageCodes.Contains (pobjDescription.ISOLanguageCode))
			{
				this._DescriptionISOLanguageCodes.Add (pobjDescription.ISOLanguageCode);
			}
		}
		
		#endregion


		#region Funciones de Fitro
		
		public List<XmlMember> GetNoArrayMembers (XmlObjectSetManager pobjObjSetMan)
		{
			List<XmlMember> objResult;
				
			
			objResult = new List<XmlMember> ();
			
			foreach (XmlMember objMember in this._Members)
			{
				if (objMember.IsBasic || !pobjObjSetMan.GetObjectBy (objMember).IsArray)
				{
					objResult.Add (objMember);
				}
			}
			
			return objResult;
		}
		
		#endregion


		#region IEquatable<XmlObject> Members

		public bool Equals (XmlObject pobjObject)
		{
			return (this.Alias == pobjObject.Alias && this.Name == pobjObject.Name);
		}

		#endregion


		#region IComparable<XmlObject> Members

		public int CompareTo (XmlObject pobjObject)
		{
			if (this.Level < pobjObject.Level) { return -1; }
			if (this.Level > pobjObject.Level) { return 1; }
			
			return 0;
		}

		#endregion
	}
}